package com.axs.logcollector.axs;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.log4j.Logger;
import org.springframework.context.support.AbstractMessageSource;

import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.SFTPv3Client;
import ch.ethz.ssh2.SFTPv3FileHandle;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;

public class ExecutorLogHandler {
	private static final Logger m_log = Logger.getLogger(ExecutorLogHandler.class);
	
	public static final String DEFAULT_AMS_USER = "amssys";
	public static final String DEFAULT_AMS_PASSWORD = "amssys";
	public static final int RETRY_COUNT = 3;
	public static final int DEFAULT_CONNECTION_TIMEOUT = 30 * 1000; // 30 seconds
	public static final int BYTE_LIMIT = 30000;
	
	private Map<String, Connection> m_connection = new HashMap<String, Connection>();

	public void coppyLogsToLocal(String serverAddress, String remoteSourceFile, String localDestFile) throws Exception {
		coppyLogsToLocal(serverAddress, DEFAULT_AMS_USER, DEFAULT_AMS_PASSWORD, remoteSourceFile, localDestFile, 0);
	}
	
	public void coppyLogsToLocal(String serverAddress, String userName, String password, String remoteSourceFile, 
			String localDestFile, long offset) throws Exception {
		Connection connection = getConnection(serverAddress, userName, password);
		SFTPv3Client sftpClient = null;
		FileOutputStream fileOS = null;
		BufferedOutputStream bufferedOS = null;
		SFTPv3FileHandle fileHandle = null;
		try {
			sftpClient = getSFTPv3Client(connection);
			// Only need to open file for reading
			fileHandle = sftpClient.openFileRO(remoteSourceFile);
			File localFile = new File(localDestFile);
			if (!localFile.getParentFile().exists()) {
				//m_logger.debug("Create local dir: " + localFile.getParent());
				localFile.getParentFile().mkdirs();
			}

			fileOS = new FileOutputStream(localFile, true);
			bufferedOS = new BufferedOutputStream(fileOS);

			if(offset < 0) {
				offset = 0;
			}

			byte[] data = new byte[BYTE_LIMIT];
			while (true) {
				int length = sftpClient.read(fileHandle, offset, data, 0, BYTE_LIMIT);
				 
				if (length == -1) {
					break;
				}
				bufferedOS.write(data, 0, length);
				bufferedOS.flush();
				offset += length;
			}
		} catch (Exception e) {
			throw e;
		} finally {
			if (fileOS != null) {
				try {
					fileOS.close();
				} catch (IOException ignore) {
				}
			}
			if (bufferedOS != null) {
				try {
					bufferedOS.close();
				} catch (IOException ignore) {
				}
			}
			if (sftpClient != null && fileHandle != null) {
				try {
					sftpClient.closeFile(fileHandle);
				} catch (IOException ignore) {
				}
				closeSFTPv3Client(sftpClient);
			}
			// Keep connection 
			//closeConnection(connection);
		}
	}
	private void closeSFTPv3Client(SFTPv3Client client) {
		try {
			if (client != null) {
				client.close();
			}
		} catch (Exception ignore) { }
	}

	private void closeConnection(Connection conn) {
		try {
			if (conn != null) {
				conn.close();
			}
		} catch (Exception ignore) {
		}
	}
	
	protected SFTPv3Client getSFTPv3Client(Connection connection) throws IOException {
		return new SFTPv3Client(connection);
	}
	
	public String executeCommand(String ipAddress, String userName, String password, String command) {
		Session session = null;
		try {
			Connection connection = getConnection(ipAddress, userName, password);
			if (connection == null) {
				m_log.info("Falied to connect to " + ipAddress);
				return null;
			}
			
			m_log.info("Oppening session to " + ipAddress);
			session = connection.openSession();

			m_log.info("Executing command: " + command);
			session.execCommand(command); 
			
			String commandResult = getCommandResult(session);
			m_log.info("Command result: " + commandResult);
			return commandResult;
		} catch( Exception e ) {
			m_log.info("Could not execute command " + command + ": " + e);
		} finally {
			m_log.info("Closing session from " + ipAddress); 
			session.close();
		}
		return null;
	}
	
	public String executeCommand(String ipAdrress, String command) {
		return executeCommand(ipAdrress, DEFAULT_AMS_USER, DEFAULT_AMS_PASSWORD, command);
	}
	
	private String getCommandResult(Session session) throws IOException {
		StringBuilder commandResult = new StringBuilder();
		
		InputStream stdout = new StreamGobbler(session.getStdout());
		BufferedReader br = new BufferedReader(new InputStreamReader(stdout));
		
		String line = br.readLine();
		while( line != null ){
			commandResult.append( line + "\n" );
			line = br.readLine();
		}
		
		return commandResult.toString();
	}
	

	public Connection connect(String hostName, String userName, String password, int retryCount) {
		while (retryCount != 0) {
			Connection connection = null;
			try {
				m_log.info("Connecting to " + hostName);
				connection = new Connection(hostName);
				connection.connect(null, DEFAULT_CONNECTION_TIMEOUT, 0);
				
				boolean isConnected = connection.authenticateWithPassword(userName, password);
				if (isConnected) {
					m_log.info("Connect to server " + hostName + " successfully");
					m_connection.put(hostName, connection);
					return connection;
				}
			} catch( Exception e ) {
				m_log.error("Could not connect to " + hostName);
				if (connection != null) {
					connection.close();
				}
				connection = null;
				retryCount--;
			}
		}
		return null;
	}
	
	private Connection getConnection(String ipAddress, String userName, String password) {
		if (m_connection.keySet().contains(ipAddress)) {
			return m_connection.get(ipAddress);
		}
		return connect(ipAddress, userName, password, RETRY_COUNT);
	}
	
	public void closeAllConnections() {
		try { 
			if (m_connection.isEmpty()) {
				for (Entry<String, Connection> enryConnection : m_connection.entrySet()) {
					m_log.info("Closing connection from " + enryConnection.getKey());
					enryConnection.getValue().close();
				}
			}
			m_connection.clear();
		} catch (Exception e) {
			m_log.error("Could not close a connection: " + e);
		}
	}
}
